package com.roc.base.mall.configuration.db;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMapping.Builder;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;

import com.roc.base.mall.common.base.BaseModel;
import com.roc.base.mall.common.base.Page;
import com.roc.base.mall.common.util.DateUtil;

/**
 * Mybatis
 * 
 */

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
public class PageInterceptor implements Interceptor {

	/**
	 * 
	 */
	@SuppressWarnings("unchecked")
	public Object intercept(Invocation invocation) throws Throwable {

		RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();

		StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");

		BoundSql boundSql = delegate.getBoundSql();
		String sql = boundSql.getSql();

		Object paramObj = boundSql.getParameterObject();
		if (paramObj instanceof HashMap) {
			HashMap<String, Object> paramArg = (HashMap<String, Object>) paramObj;
			if (paramArg.size() > 1) {
				for (Map.Entry<String, Object> paramEntry : paramArg.entrySet()) {
					Object param = paramEntry.getValue();
					if (param instanceof Page) {
						Page page = (Page) param;
						MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");

						Connection connection = (Connection) invocation.getArgs()[0];

						this.setTotalRecord(page, mappedStatement, connection, boundSql);

						String pageSql = this.getPageSql(page, sql);

						ReflectUtil.setFieldValue(boundSql, "sql", pageSql);

						break;
					}
				}
			}
		} else if (paramObj instanceof BaseModel) {
			BaseModel param = (BaseModel) paramObj;
			String nowData = DateUtil.getCurrentDate(DateUtil.YYMMDDHHMMSS);
			List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
			if (sql.startsWith("insert")) {
				param.setCreateDateTime(nowData);
				param.setLastUpdateTime(nowData);
				String insertSql = getInsertOrUpdateSql(true, sql);
				ReflectUtil.setFieldValue(boundSql, "sql", insertSql);
				Configuration configuration = (Configuration) ReflectUtil.getFieldValue(parameterMappings.get(0), "configuration");
				Builder pCreateDateTime = new Builder(configuration, "createDateTime", String.class);
				pCreateDateTime.jdbcType(JdbcType.TIMESTAMP);
				ParameterMapping pmCreateDateTime = pCreateDateTime.build();
				parameterMappings.add(pmCreateDateTime);
				Builder pUpdateDateTime = new Builder(configuration, "lastUpdateTime", String.class);
				pUpdateDateTime.jdbcType(JdbcType.TIMESTAMP);
				ParameterMapping pmUpdateDateTime = pUpdateDateTime.build();
				parameterMappings.add(pmUpdateDateTime);
			} else if (sql.startsWith("update")) {
				param.setLastUpdateTime(nowData);
				String updateSql = getInsertOrUpdateSql(false, sql);
				ReflectUtil.setFieldValue(boundSql, "sql", updateSql);
				Configuration configuration = (Configuration) ReflectUtil.getFieldValue(parameterMappings.get(0), "configuration");
				Builder pUpdateDateTime = new Builder(configuration, "lastUpdateTime", String.class);
				pUpdateDateTime.jdbcType(JdbcType.TIMESTAMP);
				ParameterMapping pmUpdateDateTime = pUpdateDateTime.build();
				int whereIndex = updateSql.indexOf("where");
				if (whereIndex != -1) {
					String whereSql = updateSql.substring(whereIndex);
					int index = counter(whereSql, '?');
					if (index > 0) {
						parameterMappings.add(parameterMappings.size() - index, pmUpdateDateTime);
					} else {
						parameterMappings.add(pmUpdateDateTime);
					}
				} else {
					parameterMappings.add(pmUpdateDateTime);
				}
			}
		}
		return invocation.proceed();
	}

	private static int counter(String s, char c) {
		int count = 0;
		for (int i = 0; i < s.length(); i++) {
			if (s.charAt(i) == c) {
				count++;
			}
		}
		return count;
	}

	private void setTotalRecord(Page page, MappedStatement mappedStatement, Connection connection, BoundSql boundSql) {

		String sql = boundSql.getSql();

		String countSql = this.getCountSql(sql);

		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

		BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, boundSql.getParameterObject());

		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql);

		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = connection.prepareStatement(countSql);
			parameterHandler.setParameters(pstmt);

			rs = pstmt.executeQuery();
			int totalRecord = 0;
			while (rs.next()) {
				totalRecord += rs.getInt(1);
			}
			page.setTotalRecord(totalRecord);
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			try {
				if (rs != null) {
					rs.close();
				}
				if (pstmt != null) {
					pstmt.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 */
	private String getCountSql(String sql) {
		sql = "select count(*) from (" + sql + ") pInter";
		return sql;

	}

	/**
	 */
	private String getPageSql(Page page, String sql) {
		StringBuffer sqlBuffer = new StringBuffer(sql);

		int offset = (page.getPageNo() - 1) * page.getPageSize();
		sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());

		return sqlBuffer.toString();
	}

	/**
	*/
	private String getInsertOrUpdateSql(boolean flag, String sql) {
		StringBuffer sqlBuffer = new StringBuffer(sql);
		if (flag) { // insert
			sqlBuffer.insert(sqlBuffer.indexOf(")"), ", create_date_time, last_update_time");
			sqlBuffer.insert(sqlBuffer.lastIndexOf(")"), ", ?, ?");
		} else { // update
			int whereIndex = sqlBuffer.indexOf("where");
			if (whereIndex != -1) {
				sqlBuffer.insert(sqlBuffer.indexOf("where"), ", last_update_time = ?  ");
			} else {
				sqlBuffer.append(", last_update_time = ? ");
			}
		}
		return sqlBuffer.toString();
	}

	/**
	 */
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	/**
	 */
	public void setProperties(Properties properties) {
	}

	/**
	 */
	private static class ReflectUtil {

		/**
		 * 
		 * @param obj
		 * @param fieldName
		 */
		public static Object getFieldValue(Object obj, String fieldName) {
			Object result = null;
			Field field = ReflectUtil.getField(obj, fieldName);
			if (field != null) {
				field.setAccessible(true);
				try {
					result = field.get(obj);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
			return result;
		}

		/**
		 * 
		 * @param obj
		 * @param fieldName
		 */
		private static Field getField(Object obj, String fieldName) {
			Field field = null;
			for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
				try {
					field = clazz.getDeclaredField(fieldName);
					break;
				} catch (NoSuchFieldException e) {
				}
			}
			return field;
		}

		/**
		 */
		public static void setFieldValue(Object obj, String fieldName, Object fieldValue) {
			Field field = ReflectUtil.getField(obj, fieldName);
			if (field != null) {
				field.setAccessible(true);
				try {
					field.set(obj, fieldValue);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
	}

}